// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package runtime

import (
	"internal/bytealg"
	"internal/goarch"
	"runtime/internal/atomic"
	"runtime/internal/sys"
	"unsafe"
)

// 该文件中的代码实现了所有体系结构的堆栈跟踪遍历。
// 关于给定架构，最重要的事实是它是否使用链接寄存器。
// 在带有链接寄存器的系统上，非叶函数的序言将LR的传入值存储在新分配的堆栈帧的底部。
// 在没有链接寄存器（x86）的系统上，该体系结构在调用指令期间推送返回PC，因此返回PC最终位于堆栈框架之上。
// 在这个文件中，返回的PC始终被称为LR，无论它是如何找到的。

const usesLR = sys.MinFrameSize > 0

// 通用回溯。处理运行时堆栈打印（pcbuf==nil），
// 运行时。调用者函数（pcbuf！=nil）以及垃圾
// 收集器（callback！=nil）。合并这些代码有点笨拙，但避免了
// 重复代码及其所有微妙之处。
// 
// skip参数仅对pcbuf有效！=nil并计算要跳过的逻辑帧而不是物理帧的数量
// （通过内联，pcbuf中的
// PC可以代表多个调用）。
func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max int, callback func(*stkframe, unsafe.Pointer) bool, v unsafe.Pointer, flags uint) int {
	if skip > 0 && callback != nil {
		throw("gentraceback callback cannot be used with non-zero skip")
	}

	// 不要称之为“g”；很容易混淆“g”和“gp”。
	if ourg := getg(); ourg == gp && ourg == ourg.m.curg {
		// 起始sp已作为uintptr传入，调用方可能也有其他uintptr类型的堆栈引用。
		// 如果在使我们到达这里的一个调用期间或在
		// 堆栈下的一个回调期间必须增长，那么所有这些对堆栈的uintptr引用
		// 将不会更新，gentraceback将继续
		// 检查旧堆栈内存，这可能不再有效。
		// 即使所有变量都已正确更新，也不清楚
		// 我们想公开一个从一个堆栈开始到另一个堆栈结束的回溯。这可能会让打电话的人感到困惑。
		// 相反，我们要求gentraceback和任何其他
		// 为当前goroutine（通常由
		// 调用getcallersp获得）接受sp的函数不能在该goroutine的堆栈上运行，而应在g0堆栈上运行
		// 。
		throw("gentraceback cannot trace user goroutine on its own stack")
	}
	level, _, _ := gotraceback()

	var ctxt *funcval // 未启动的goroutine的上下文指针。见第25897期。

	if pc0 == ^uintptr(0) && sp0 == ^uintptr(0) { // 从gp获取保存值的信号。
		if gp.syscallsp != 0 {
			pc0 = gp.syscallpc
			sp0 = gp.syscallsp
			if usesLR {
				lr0 = 0
			}
		} else {
			pc0 = gp.sched.pc
			sp0 = gp.sched.sp
			if usesLR {
				lr0 = gp.sched.lr
			}
			ctxt = (*funcval)(gp.sched.ctxt)
		}
	}

	nprint := 0
	var frame stkframe
	frame.pc = pc0
	frame.sp = sp0
	if usesLR {
		frame.lr = lr0
	}
	waspanic := false
	cgoCtxt := gp.cgoCtxt
	printing := pcbuf == nil && callback == nil

	// 如果PC为零，则可能是一个nil函数调用。
	// 从调用方的帧开始。
	if frame.pc == 0 {
		if usesLR {
			frame.pc = *(*uintptr)(unsafe.Pointer(frame.sp))
			frame.lr = 0
		} else {
			frame.pc = uintptr(*(*uintptr)(unsafe.Pointer(frame.sp)))
			frame.sp += goarch.PtrSize
		}
	}

	// 运行时/内部/原子函数调用
	// arm<7上的内核帮助程序。请参阅runtime/internal/atomic/sys_linux_arm。s、 
	// 
	// 从调用方的帧开始。
	if GOARCH == "arm" && goarm < 7 && GOOS == "linux" && frame.pc&0xffff0000 == 0xffff0000 {
		// 请注意，这些调用是简单的BL，没有返回
		// 地址，因此我们直接使用LR。
		// 
		// 内核助手是无框架叶函数，因此SP和
		// LR不受影响。
		frame.pc = frame.lr
		frame.lr = 0
	}

	f := findfunc(frame.pc)
	if !f.valid() {
		if callback != nil || printing {
			print("runtime: unknown pc ", hex(frame.pc), "\n")
			tracebackHexdump(gp.stack, &frame, 0)
		}
		if callback != nil {
			throw("unknown pc")
		}
		return 0
	}
	frame.fn = f

	var cache pcvalueCache

	lastFuncID := funcID_normal
	n := 0
	for n < max {
		// 通常：
		// pc是运行函数的pc。
		// sp是指向该程序计数器的堆栈指针。
		// fp是该程序计数器上的帧指针（调用者的堆栈指针），如果未知，则为nil。
		// stk是包含sp的堆栈。
		// 调用方的程序计数器是lr，除非lr为零，在这种情况下它是*（uintptpr*）sp。
		f = frame.fn
		if f.pcsp == 0 {
			// 无帧信息，必须是外部函数，如种族支持。
			// 见戈朗。org/issue/13568。
			break
		}

		// 计算函数信息标志。
		flag := f.flag
		if f.funcID == funcID_cgocallback {
			// cgocallback写入SP以从g0切换到curg堆栈，
			// 但它仔细安排，在转换过程中，两个堆栈
			// 都有cgocallback框架，可以通过该框架展开。
			// 所以我们不需要将其与其他SP写入函数一起排除。
			flag &^= funcFlag_SPWRITE
		}
		if frame.pc == pc0 && frame.sp == sp0 && pc0 == gp.syscallpc && sp0 == gp.syscallsp {
			// 一些系统调用函数会写入SP，但它们只有在
			// 使用entersyscall保存输入PC/SP后才会写入。
			// 因为我们使用的是PC/SP条目，所以后面的SP写入并不重要。
			flag &^= funcFlag_SPWRITE
		}

		// 找到了一个实际函数。
		// 导出帧指针和链接寄存器。
		if frame.fp == 0 {
			// 跳过系统堆栈转换。如果我们在g0上，有一个用户
			// goroutine，尝试跳转。否则这是一个常规电话。
			if flags&_TraceJumpStack != 0 && gp == gp.m.g0 && gp.m.curg != nil {
				switch f.funcID {
				case funcID_morestack:
					// morestack无法正常返回--newstack（）
					// gogo's to curg。附表。与之匹配。
					// 这可以防止morestack（）出现在回溯中，
					// 但这是有道理的，因为它永远不会被返回到
					// 中。
					frame.pc = gp.m.curg.sched.pc
					frame.fn = findfunc(frame.pc)
					f = frame.fn
					flag = f.flag
					frame.sp = gp.m.curg.sched.sp
					cgoCtxt = gp.m.curg.cgoCtxt
				case funcID_systemstack:
					// 系统堆栈正常返回，所以只需遵循
					// 堆栈转换即可。
					frame.sp = gp.m.curg.sched.sp
					cgoCtxt = gp.m.curg.cgoCtxt
					flag &^= funcFlag_SPWRITE
				}
			}
			frame.fp = frame.sp + uintptr(funcspdelta(f, frame.pc, &cache))
			if !usesLR {
				// 在x86上，call指令在进入新函数之前推动返回PC。
				frame.fp += goarch.PtrSize
			}
		}
		var flr funcInfo
		if flag&funcFlag_TOPFRAME != 0 {
			// 此函数标记堆栈的顶部。停止追踪。
			frame.lr = 0
			flr = funcInfo{}
		} else if flag&funcFlag_SPWRITE != 0 && (callback == nil || n > 0) {
			// 我们使用的函数对SP进行写入，但我们不知道
			// 如何在spdelta表中进行编码。例子包括上下文
			// 像runtime这样的切换例程。gogo，但也包括任何将
			// 切换到g0堆栈以运行主机C代码的代码。由于我们无法可靠地释放SP（我们甚至可能不在我们认为的堆栈中），我们在这里停止回溯。
			// 这只适用于分析信号（回调==nil）。
			// 
			// 对于GC堆栈遍历（callback！=nil），我们应该只看到
			// 一个在堆栈增长检查期间自动抢占条目
			// 的函数。在这种情况下，函数的
			// 尚未有机会对SP进行任何写入，因此可以安全地释放。
			// isAsyncSafePoint不允许程序集函数被异步抢占，
			// 和preemptPark双重检查SPWRITE函数是否被异步抢占。
			// 所以对于GC堆栈遍历，我们不考虑任何事情（如果主体不执行n==0），
			// 位于堆栈的底部框架。但再往上看，我们最好不要找到任何。
			if callback != nil {
				println("traceback: unexpected SPWRITE function", funcname(f))
				throw("traceback")
			}
			frame.lr = 0
			flr = funcInfo{}
		} else {
			var lrPtr uintptr
			if usesLR {
				if n == 0 && frame.sp < frame.fp || frame.lr == 0 {
					lrPtr = frame.sp
					frame.lr = *(*uintptr)(unsafe.Pointer(lrPtr))
				}
			} else {
				if frame.lr == 0 {
					lrPtr = frame.fp - goarch.PtrSize
					frame.lr = uintptr(*(*uintptr)(unsafe.Pointer(lrPtr)))
				}
			}
			flr = findfunc(frame.lr)
			if !flr.valid() {
				// 如果您在错误的时间得到分析中断，就会发生这种情况。
				// 在这种情况下，提前停止是可以的。
				// 但如果设置了回调，我们正在进行垃圾收集，必须获取所有内容，所以大声崩溃。
				doPrint := printing
				if doPrint && gp.m.incgo && f.funcID == funcID_sigpanic {
					// 我们可以将Sigmanic 
					// 调用直接注入C代码，
					// 在这种情况下，我们将看到一个C 
					// 返回PC。不要抱怨。
					doPrint = false
				}
				if callback != nil || doPrint {
					print("runtime: unexpected return pc for ", funcname(f), " called from ", hex(frame.lr), "\n")
					tracebackHexdump(gp.stack, &frame, lrPtr)
				}
				if callback != nil {
					throw("unknown caller pc")
				}
			}
		}

		frame.varp = frame.fp
		if !usesLR {
			// 在x86上，call指令在进入新函数之前推动返回PC。
			frame.varp -= goarch.PtrSize
		}

		// 对于带有帧指针的架构，如果有
		// 一个帧，那么这里有一个保存的帧指针。
		// 
		// 注意：这段代码并不像看上去那么通用。
		// 在x86上，ABI将把帧指针字保存在堆栈帧的
		// 顶部，因此我们必须后退。
		// 在arm64上，帧指针应该位于堆栈的底部（R29（aka FP）=RSP），在这种情况下，我们不想在这里进行减法运算。但是我们一开始没有任何帧指针，当我们想添加它时，我们不想中断所有直接写入8（RSP）的程序集，将第一个参数设置为一个被调用的函数。
		// 所以我们决定在堆栈指针
		// （在Go函数中R29=RSP-8）下面写FP-link*。
		// 这在技术上与ABI兼容，但不是标准配置。
		// 它恰好模仿了x86的布局。
		// 其他架构可能会做出不同的决定。
		if frame.varp > frame.sp && framepointer_enabled {
			frame.varp -= goarch.PtrSize
		}

		// 导出参数的大小。
		// 大多数函数都有一个固定大小的参数块，
		// 因此我们可以使用关于函数f的元数据。
		// 但不是所有函数：在包运行时和反射中有一些变量函数
		// 对于这些函数，我们使用调用方记录的特定于调用的
		// 元数据。wen jian defg
		if callback != nil || printing {
			frame.argp = frame.fp + sys.MinFrameSize
			var ok bool
			frame.arglen, frame.argmap, ok = getArgInfoFast(f, callback != nil)
			if !ok {
				frame.arglen, frame.argmap = getArgInfo(&frame, f, callback != nil, ctxt)
			}
		}

		// 通常这是堆栈上的返回地址，但如果sigpanic 
		// 位于堆栈上此函数的正下方，则帧
		// 由于陷阱而停止执行，帧。pc可能不是
		// 查找活动信息的安全点。在这种令人恐慌的情况下，
		// 函数要么根本不返回（如果它没有延迟，或者如果
		// 延迟没有恢复），要么从对
		// 延迟过程的一次调用返回（如果相应的延迟函数恢复）。
		// 在后一种情况下，使用延迟返回调用站点作为延续pc。
		frame.continpc = frame.pc
		if waspanic {
			if frame.fn.deferreturn != 0 {
				frame.continpc = frame.fn.entry() + uintptr(frame.fn.deferreturn) + 1
				// 注意：这可能会使返回变量比
				// 严格必要，因为我们使用“函数有一个延迟语句”
				// 作为“函数实际延迟了某事”的代理。这似乎是一个小缺点。（我们过去经常查看
				// gp.\u defer for a defer for a与此函数相对应的延迟，但是在堆栈复制期间，
				// 很难处理堆栈上的延迟记录。）
				// 注意：+1是用来抵消
				// 堆栈中的-1。go:getStackMap不备份返回
				// 地址确保电脑在CALL指令中。
			} else {
				frame.continpc = 0
			}
		}

		if callback != nil {
			if !callback((*stkframe)(noescape(unsafe.Pointer(&frame))), v) {
				return n
			}
		}

		if pcbuf != nil {
			pc := frame.pc
			// 备份到调用指令以读取内联信息（逻辑与下面相同）
			tracepc := pc
			// 通常，pc是一个返回地址。在这种情况下，我们希望使用pc-1查找
			// 文件/行信息，因为这是
			// call指令的pc（更准确地说，是call指令的最后一个字节）。
			// 呼叫者希望pc缓冲区包含返回地址，并自己执行
			// 相同的-1操作，因此我们保持pc不变。
			// 当pc来自信号（例如探查器或segv）时，我们希望
			// 使用pc查找文件/行信息，并将pc+1存储在
			// pc缓冲区中，以便呼叫者在查找前无条件地减去1。
			// 见第34123期。
			// 在没有实际运行代码（如运行时）的情况下初始化帧时，pc可以处于函数入口。mstart。
			if (n == 0 && flags&_TraceTrap != 0) || waspanic || pc == f.entry() {
				pc++
			} else {
				tracepc--
			}

			// 如果有内联信息，记录内部帧。
			if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
				inltree := (*[1 << 20]inlinedCall)(inldata)
				for {
					ix := pcdatavalue(f, _PCDATA_InlTreeIndex, tracepc, &cache)
					if ix < 0 {
						break
					}
					if inltree[ix].funcID == funcID_wrapper && elideWrapperCalling(lastFuncID) {
						// 忽略包装
					} else if skip > 0 {
						skip--
					} else if n < max {
						(*[1 << 20]uintptr)(unsafe.Pointer(pcbuf))[n] = pc
						n++
					}
					lastFuncID = inltree[ix].funcID
					// 备份到“调用者”中的指令。
					tracepc = frame.fn.entry() + uintptr(inltree[ix].parentPc)
					pc = tracepc + 1
				}
			}
			// 记录主画面。
			if f.funcID == funcID_wrapper && elideWrapperCalling(lastFuncID) {
				// 忽略包装函数（除非它们触发恐慌）。
			} else if skip > 0 {
				skip--
			} else if n < max {
				(*[1 << 20]uintptr)(unsafe.Pointer(pcbuf))[n] = pc
				n++
			}
			lastFuncID = f.funcID
			n-- // 偏移量n++低于
		}

		if printing {
			// 假设跳过=0进行打印。
			// 
			// 如果我们没有打印任何框架，请不要删除包装。不要省略
			// 调用的包装，而不是包装的
			// 函数。否则，请将其排除在外。

			// 备份到调用指令以读取内联信息（逻辑与下面相同）
			tracepc := frame.pc
			if (n > 0 || flags&_TraceTrap == 0) && frame.pc > f.entry() && !waspanic {
				tracepc--
			}
			// 如果有内联信息，则打印内部帧。
			if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
				inltree := (*[1 << 20]inlinedCall)(inldata)
				var inlFunc _func
				inlFuncInfo := funcInfo{&inlFunc, f.datap}
				for {
					ix := pcdatavalue(f, _PCDATA_InlTreeIndex, tracepc, nil)
					if ix < 0 {
						break
					}

					// 为
					// 内联函数创建一个假函数。
					inlFunc.nameoff = inltree[ix].func_
					inlFunc.funcID = inltree[ix].funcID

					if (flags&_TraceRuntimeFrames) != 0 || showframe(inlFuncInfo, gp, nprint == 0, inlFuncInfo.funcID, lastFuncID) {
						name := funcname(inlFuncInfo)
						file, line := funcline(f, tracepc)
						print(name, "(...)\n")
						print("\t", file, ":", line, "\n")
						nprint++
					}
					lastFuncID = inltree[ix].funcID
					// 备份到“调用者”中的指令。
					tracepc = frame.fn.entry() + uintptr(inltree[ix].parentPc)
				}
			}
			if (flags&_TraceRuntimeFrames) != 0 || showframe(f, gp, nprint == 0, f.funcID, lastFuncID) {
				// 崩溃时打印。
				// main（0x1，0x2，0x3）
				// /home/rsc/go/src/runtime/x.go:23+0xf 
				// 
				name := funcname(f)
				file, line := funcline(f, tracepc)
				if name == "runtime.gopanic" {
					name = "panic"
				}
				print(name, "(")
				argp := unsafe.Pointer(frame.argp)
				printArgs(f, argp, tracepc)
				print(")\n")
				print("\t", file, ":", line)
				if frame.pc > f.entry() {
					print(" +", hex(frame.pc-f.entry()))
				}
				if gp.m != nil && gp.m.throwing > 0 && gp == gp.m.curg || level >= 2 {
					print(" fp=", hex(frame.fp), " sp=", hex(frame.sp), " pc=", hex(frame.pc))
				}
				print("\n")
				nprint++
			}
			lastFuncID = f.funcID
		}
		n++

		if f.funcID == funcID_cgocallback && len(cgoCtxt) > 0 {
			ctxt := cgoCtxt[len(cgoCtxt)-1]
			cgoCtxt = cgoCtxt[:len(cgoCtxt)-1]

			// skip仅适用于go帧。
			// 回调！=nil仅在我们只关心
			// 围棋框架时使用。
			if skip == 0 && callback == nil {
				n = tracebackCgoContext(pcbuf, printing, ctxt, n, max)
			}
		}

		waspanic = f.funcID == funcID_sigpanic
		injectedCall := waspanic || f.funcID == funcID_asyncPreempt

		// 不要从堆栈底部展开。
		if !flr.valid() {
			break
		}

		// 退到下一帧。
		frame.fn = flr
		frame.pc = frame.lr
		frame.lr = 0
		frame.sp = frame.fp
		frame.fp = 0
		frame.argmap = nil

		// 在链路寄存器体系结构上，sighandler在假装调用之前将LR保存在堆栈上。
		if usesLR && injectedCall {
			x := *(*uintptr)(unsafe.Pointer(frame.sp))
			frame.sp += alignUp(sys.MinFrameSize, sys.StackAlign)
			f = findfunc(frame.pc)
			frame.fn = f
			if !f.valid() {
				frame.pc = x
			} else if funcspdelta(f, frame.pc, &cache) == 0 {
				frame.lr = x
			}
		}
	}

	if printing {
		n = nprint
	}

	// 注意恐慌！=nil在这里没有问题：可能会有剩余的恐慌，因为恐慌堆栈上的延迟不会像延迟堆栈上的延迟那样按帧顺序嵌套。如果你有：
	// 
	// 1帧延迟d1 
	// 2帧延迟d2 
	// 3帧延迟d3 
	// 4帧恐慌
	// 4帧恐慌开始运行延迟
	// 5帧，运行d3，延迟d4 
	// frame 5恐慌
	// frame 5的恐慌开始运行延迟
	// frame 6，运行d4，垃圾收集
	// frame 6，运行d2，垃圾收集
	// 
	// d4执行期间，恐慌堆栈为d4->d3，其中
	// ，我们将把第三帧视为可恢复帧，因为我们可以找到d3。（事实上，第3帧是可恢复的。如果d4恢复
	// 并且第5帧继续运行，d3、d3可以恢复，我们将
	// 在第3帧中恢复执行。）然而，在执行d2时，死机堆栈是d2->d3，这是反向的。扫描会将d2匹配到第2帧，但在堆栈上有
	// d2，直到那时，这意味着它不会将d3匹配到第3帧。
	// 这没关系：如果我们运行的是d2，那么d2之后的所有延迟都已完成，并且它们对应的帧已失效。对于第3帧，没有找到d3 
	// 意味着我们将设置第3帧的continpc==0，这是正确的
	// （第3帧已失效）。因此，在步行结束时，惊慌失措的人群可能会
	// 包含死帧的延迟（本例中为d3）。这里的反转
	// 总是表示一个死帧，而反转对
	// 扫描的影响是隐藏那些死帧，所以扫描仍然是正常的：
	// 恐慌堆栈上剩下的正是（而且只是）死帧。
	// 
	// 我们需要回拨！=这里为零，因为只有在回调时！=nil 
	// 我们知道gentraceback是在“必须正确”的上下文中被调用的，而不是在“尽力”的上下文中。
	// 回调只有在一切正常停止时才会发生。
	// 在其他情况下，例如为分析信号收集堆栈时，或者在崩溃期间打印回溯时，一切可能都不顺利
	// 停止得很好，堆栈漫游可能无法完成。
	if callback != nil && n < max && frame.sp != gp.stktopsp {
		print("runtime: g", gp.goid, ": frame.sp=", hex(frame.sp), " top=", hex(gp.stktopsp), "\n")
		print("\tstack=[", hex(gp.stack.lo), "-", hex(gp.stack.hi), "] n=", n, " max=", max, "\n")
		throw("traceback did not unwind completely")
	}

	return n
}

// printArgs在回溯中打印函数参数。
func printArgs(f funcInfo, argp unsafe.Pointer, pc uintptr) {
	// 参数打印的“指令”用_FUNCDATA_ArgInfo编码。
	// 参见cmd/compile/internal/ssagen。emitArgInfo用于描述
	// 编码。
	// 这些常量需要与编译器同步。
	const (
		_endSeq         = 0xff
		_startAgg       = 0xfe
		_endAgg         = 0xfd
		_dotdotdot      = 0xfc
		_offsetTooLarge = 0xfb
	)

	const (
		limit    = 10                       // 打印不超过10个参数/组件
		maxDepth = 5                        // 不超过5层嵌套
		maxLen   = (maxDepth*3+2)*limit + 1 // 最大_FUNCDATA_ArgInfo长度（参见编译器端的推理）
	)

	p := (*[maxLen]uint8)(funcdata(f, _FUNCDATA_ArgInfo))
	if p == nil {
		return
	}

	liveInfo := funcdata(f, _FUNCDATA_ArgLiveInfo)
	liveIdx := pcdatavalue(f, _PCDATA_ArgLiveIndex, pc, nil)
	startOffset := uint8(0xff) // 需要活动性信息的最小偏移量（偏移量较低的插槽总是活动的）
	if liveInfo != nil {
		startOffset = *(*uint8)(liveInfo)
	}

	isLive := func(off, slotIdx uint8) bool {
		if liveInfo == nil || liveIdx <= 0 {
		}
		if off < startOffset {
			return true
		}
		bits := *(*uint8)(add(liveInfo, uintptr(liveIdx)+uintptr(slotIdx/8)))
		return bits&(1<<(slotIdx%8)) != 0
	}

	print1 := func(off, sz, slotIdx uint8) {
		x := readUnaligned64(add(argp, uintptr(off)))
		// 屏蔽无关位
		if sz < 8 {
			shift := 64 - sz*8
			if goarch.BigEndian {
				x = x >> shift
			} else {
				x = x << shift >> shift
			}
		}
		print(hex(x))
		if !isLive(off, slotIdx) {
			print("?")
		}
	}

	start := true
	printcomma := func() {
		if !start {
			print(", ")
		}
	}
	pi := 0
	slotIdx := uint8(0) // 寄存器arg溢出插槽索引
printloop:
	for {
		o := p[pi]
		pi++
		switch o {
		case _endSeq:
			break printloop
		case _startAgg:
			printcomma()
			print("{")
			start = true
			continue
		case _endAgg:
			print("}")
		case _dotdotdot:
			printcomma()
			print("...")
		case _offsetTooLarge:
			printcomma()
			print("_")
		default:
			printcomma()
			sz := p[pi]
			pi++
			print1(o, sz, slotIdx)
			if o >= startOffset {
				slotIdx++
			}
		}
		start = false
	}
}

// reflectMethodValue是reflect的部分副本。makeFuncImpl 
// 并进行反思。方法值。
type reflectMethodValue struct {
	fn     uintptr
	stack  *bitvector // 参数和结果的ptrmap 
	argLen uintptr    // 仅参数
}

// getArgInfoFast返回调用f的参数帧信息。
// 它很短且不可内联。然而，它并不能处理所有功能。
// 如果ok报告为false，则必须调用getArgInfo。
// TODO（josharian）：一旦我们进行了堆栈中的内联，
// 直接从getArgInfoFast调用getArgInfo，并停止返回ok bool。
func getArgInfoFast(f funcInfo, needArgMap bool) (arglen uintptr, argmap *bitvector, ok bool) {
	return uintptr(f.args), nil, !(needArgMap && f.args == _ArgsSizeUnknown)
}

// getArgInfo返回调用f 
// with call frame的参数帧信息。
// 
// 这既用于具有活动堆栈帧的实际调用，也用于尚未执行的延迟调用或goroutine。如果这是一个实际的
// 调用，ctxt必须为零（getArgInfo将从
// 活动堆栈帧中检索所需内容）。如果这是一个延迟调用或未启动的goroutine，则
// ctxt必须是延迟或go'd的函数对象。
func getArgInfo(frame *stkframe, f funcInfo, needArgMap bool, ctxt *funcval) (arglen uintptr, argmap *bitvector) {
	arglen = uintptr(f.args)
	if needArgMap && f.args == _ArgsSizeUnknown {
		// 从为反射而进行的调用中提取反射存根的参数位图。
		switch funcname(f) {
		case "reflect.makeFuncStub", "reflect.methodValueCall":
			// 这些都是一个*反映。methodValue作为其
			// 上下文寄存器。
			var mv *reflectMethodValue
			var retValid bool
			if ctxt != nil {
				// 这不是一个实际的呼叫，而是一个延迟呼叫或一个未启动的goroutine。
				// 函数值本身就是*的反映。方法值。
				mv = (*reflectMethodValue)(unsafe.Pointer(ctxt))
			} else {
				// /这是一个真正的电话，需要
				// *反映。methodValue作为其上下文
				// 注册并立即将其保存到0（SP）。从
				// 0（SP）获取methodValue。
				arg0 := frame.sp + sys.MinFrameSize
				mv = *(**reflectMethodValue)(unsafe.Pointer(arg0))
				// 判断返回值是否有效。
				// Reflect在复制返回值中的
				// 后将更新此值。
				retValid = *(*bool)(unsafe.Pointer(arg0 + 4*goarch.PtrSize))
			}
			if mv.fn != f.entry() {
				print("runtime: confused by ", funcname(f), "\n")
				throw("reflect mismatch")
			}
			bv := mv.stack
			arglen = uintptr(bv.n * goarch.PtrSize)
			if !retValid {
				arglen = uintptr(mv.argLen) &^ (goarch.PtrSize - 1)
			}
			argmap = bv
		}
	}
	return
}

// tracebackCgoContext处理从
// 上下文参数到setCgoTraceback函数的cgo上下文值的跟踪。它返回n.
func tracebackCgoContext(pcbuf *uintptr, printing bool, ctxt uintptr, n, max int) int {
	var cgoPCs [32]uintptr
	cgoContextPCs(ctxt, cgoPCs[:])
	var arg cgoSymbolizerArg
	anySymbolized := false
	for _, pc := range cgoPCs {
		if pc == 0 || n >= max {
			break
		}
		if pcbuf != nil {
			(*[1 << 20]uintptr)(unsafe.Pointer(pcbuf))[n] = pc
		}
		if printing {
			if cgoSymbolizer == nil {
				print("non-Go function at pc=", hex(pc), "\n")
			} else {
				c := printOneCgoTraceback(pc, max-n, &arg)
				n += c - 1 // /+1的新值。在
				anySymbolized = true
			}
		}
		n++
	}
	if anySymbolized {
		arg.pc = 0
		callCgoSymbolizer(&arg)
	}
	return n
}

func printcreatedby(gp *g) {
	// /的下面几行显示是什么创建了goroutine，除了主goroutine（goid 1）。
	pc := gp.gopc
	f := findfunc(pc)
	if f.valid() && showframe(f, gp, false, funcID_normal, funcID_normal) && gp.goid != 1 {
		printcreatedby1(f, pc)
	}
}

func printcreatedby1(f funcInfo, pc uintptr) {
	print("created by ", funcname(f), "\n")
	tracepc := pc // 备份以调用函数行的指令。
	if pc > f.entry() {
		tracepc -= sys.PCQuantum
	}
	file, line := funcline(f, tracepc)
	print("\t", file, ":", line)
	if pc > f.entry() {
		print(" +", hex(pc-f.entry()))
	}
	print("\n")
}

func traceback(pc, sp, lr uintptr, gp *g) {
	traceback1(pc, sp, lr, gp, 0)
}

// tracebacktrap类似于traceback，但希望PC和SP是从陷阱获得的，而不是从gp->sched或gp->syscallpc/gp->syscallsp或getcallersp/getcallersp。
// 因为它们来自陷阱，而不是来自已保存的对，
// 不得将初始PC倒转到上一条指令。
// （所有保存的对都记录了一个作为返回地址的PC，因此我们
// 将其回放到CALL指令中。）
// 如果是gp。m、 libcall{g，pc，sp}信息可用，它优先使用该信息而不是
// 传入的pc/sp/lr。
func tracebacktrap(pc, sp, lr uintptr, gp *g) {
	if gp.m.libcallsp != 0 {
		// 我们在某个地方使用C代码，从保存的位置进行回溯。
		traceback1(gp.m.libcallpc, gp.m.libcallsp, 0, gp.m.libcallg.ptr(), 0)
		return
	}
	traceback1(pc, sp, lr, gp, _TraceTrap)
}

func traceback1(pc, sp, lr uintptr, gp *g, flags uint) {
	// 如果goroutine在cgo中，我们有cgo回溯，请打印出来。
	if iscgo && gp.m != nil && gp.m.ncgo > 0 && gp.syscallsp != 0 && gp.m.cgoCallers != nil && gp.m.cgoCallers[0] != 0 {
		// 锁定cgoCallers，使信号处理程序不会
		// 更改、复制阵列、重置阵列、解锁阵列。
		// 我们被锁定到线程，并且没有与信号处理程序同时运行
		// 。ABCFDG 
		atomic.Store(&gp.m.cgoCallersUse, 1)
		cgoCallers := *gp.m.cgoCallers
		gp.m.cgoCallers[0] = 0
		atomic.Store(&gp.m.cgoCallersUse, 0)

		printCgoTraceback(&cgoCallers)
	}

	if readgstatus(gp)&^_Gscan == _Gsyscall {
		// 如果在系统调用中被阻止，则覆盖寄存器。
		pc = gp.syscallpc
		sp = gp.syscallsp
		flags &^= _TraceTrap
	}
	if gp.m != nil && gp.m.vdsoSP != 0 {
		// 如果在VDSO中运行，则覆盖寄存器。这是在
		// /\u Gsyscall检查之后进行的，以覆盖entersyscall之后的VDSO呼叫。
		pc = gp.m.vdsoPC
		sp = gp.m.vdsoSP
		flags &^= _TraceTrap
	}

	// 打印回溯。默认情况下，忽略运行时帧。
	// 如果这意味着我们什么也不打印，重复强制打印所有帧。
	n := gentraceback(pc, sp, lr, gp, 0, nil, _TracebackMaxFrames, nil, nil, flags)
	if n == 0 && (flags&_TraceRuntimeFrames) == 0 {
		n = gentraceback(pc, sp, lr, gp, 0, nil, _TracebackMaxFrames, nil, nil, flags|_TraceRuntimeFrames)
	}
	if n == _TracebackMaxFrames {
		print("...additional frames elided...\n")
	}
	printcreatedby(gp)

	if gp.ancestors == nil {
		return
	}
	for _, ancestor := range *gp.ancestors {
		printAncestorTraceback(ancestor)
	}
}

// printAncestorTraceback打印给定祖先的回溯。
// TODO:将其与gentraceback和CallersFrame统一起来。
func printAncestorTraceback(ancestor ancestorInfo) {
	print("[originating from goroutine ", ancestor.goid, "]:\n")
	for fidx, pc := range ancestor.pcs {
		f := findfunc(pc) // f之前验证的
		if showfuncinfo(f, fidx == 0, funcID_normal, funcID_normal) {
			printAncestorTracebackFuncInfo(f, pc)
		}
	}
	if len(ancestor.pcs) == _TracebackMaxFrames {
		print("...additional frames elided...\n")
	}
	// 显示是什么创建了goroutine，除了主goid 1。
	f := findfunc(ancestor.gopc)
	if f.valid() && showfuncinfo(f, false, funcID_normal, funcID_normal) && ancestor.goid != 1 {
		printcreatedby1(f, ancestor.gopc)
	}
}

// printAncestorTraceback在给定的pc上打印给定的函数信息
// 。由于在创建呼叫方
// goroutine。
// goroutine时只能访问PC，因此此信息的精度降低了
func printAncestorTracebackFuncInfo(f funcInfo, pc uintptr) {
	name := funcname(f)
	if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
		inltree := (*[1 << 20]inlinedCall)(inldata)
		ix := pcdatavalue(f, _PCDATA_InlTreeIndex, pc, nil)
		if ix >= 0 {
			name = funcnameFromNameoff(f, inltree[ix].func_)
		}
	}
	file, line := funcline(f, pc)
	if name == "runtime.gopanic" {
		name = "panic"
	}
	print(name, "(...)\n")
	print("\t", file, ":", line)
	if pc > f.entry() {
		print(" +", hex(pc-f.entry()))
	}
	print("\n")
}

func callers(skip int, pcbuf []uintptr) int {
	sp := getcallersp()
	pc := getcallerpc()
	gp := getg()
	var n int
	systemstack(func() {
		n = gentraceback(pc, sp, 0, gp, skip, &pcbuf[0], len(pcbuf), nil, nil, 0)
	})
	return n
}

func gcallers(gp *g, skip int, pcbuf []uintptr) int {
	return gentraceback(^uintptr(0), ^uintptr(0), 0, gp, skip, &pcbuf[0], len(pcbuf), nil, nil, 0)
}

// showframe报告在回溯过程中是否应打印具有给定特征的框架。
func showframe(f funcInfo, gp *g, firstFrame bool, funcID, childID funcID) bool {
	g := getg()
	if g.m.throwing > 0 && gp != nil && (gp == g.m.curg || gp == g.m.caughtsig.ptr()) {
		return true
	}
	return showfuncinfo(f, firstFrame, funcID, childID)
}

// showfuncinfo报告在回溯过程中是否应打印具有给定特征的函数。
func showfuncinfo(f funcInfo, firstFrame bool, funcID, childID funcID) bool {
	// 注意，f可能是内联
	// 函数的合成funcInfo，在这种情况下，只设置了nameoff和funcID。

	level, _, _ := gotraceback()
	if level > 1 {
		// 显示所有帧。
		return true
	}

	if !f.valid() {
		return false
	}

	if funcID == funcID_wrapper && elideWrapperCalling(childID) {
		return false
	}

	name := funcname(f)

	// 特殊情况：始终显示运行时。GopANIC框架ABCFDG
	// 见golang。org/issue/5832。
	if name == "runtime.gopanic" && !firstFrame {
		return true
	}

	return bytealg.IndexByteString(name, '.') >= 0 && (!hasPrefix(name, "runtime.") || isExportedRuntime(name))
}

// isExportedRuntime报告name是否为导出的运行时函数。
// 它只适用于运行时函数，所以ASCII A-Z可以。
func isExportedRuntime(name string) bool {
	const n = len("runtime.")
	return len(name) > n && name[:n] == "runtime." && 'A' <= name[n] && name[n] <= 'Z'
}

// elideWrapperCalling报告是否应从堆栈跟踪中删除调用
// 函数id的包装函数。
func elideWrapperCalling(id funcID) bool {
	// 如果包装器调用了一个panic函数，而不是
	// 包装的函数，我们希望将其包含在堆栈中。
	return !(id == funcID_gopanic || id == funcID_sigpanic || id == funcID_panicwrap)
}

var gStatusStrings = [...]string{
	_Gidle:      "idle",
	_Grunnable:  "runnable",
	_Grunning:   "running",
	_Gsyscall:   "syscall",
	_Gwaiting:   "waiting",
	_Gdead:      "dead",
	_Gcopystack: "copystack",
	_Gpreempted: "preempted",
}

func goroutineheader(gp *g) {
	gpstatus := readgstatus(gp)

	isScan := gpstatus&_Gscan != 0
	gpstatus &^= _Gscan // 删除扫描位

	// 基本字符串状态
	var status string
	if 0 <= gpstatus && gpstatus < uint32(len(gStatusStrings)) {
		status = gStatusStrings[gpstatus]
	} else {
		status = "???"
	}

	// 覆盖。
	if gpstatus == _Gwaiting && gp.waitreason != waitReasonZero {
		status = gp.waitreason.String()
	}

	// 大约G被阻止的时间，以分钟为单位
	var waitfor int64
	if (gpstatus == _Gwaiting || gpstatus == _Gsyscall) && gp.waitsince != 0 {
		waitfor = (nanotime() - gp.waitsince) / 60e9
	}
	print("goroutine ", gp.goid, " [", status)
	if isScan {
		print(" (scan)")
	}
	if waitfor >= 1 {
		print(", ", waitfor, " minutes")
	}
	if gp.lockedm != 0 {
		print(", locked to thread")
	}
	print("]:\n")
}

func tracebackothers(me *g) {
	level, _, _ := gotraceback()

	// 如果我们还没有，请先显示当前的goroutine。
	curgp := getg().m.curg
	if curgp != nil && curgp != me {
		print("\n")
		goroutineheader(curgp)
		traceback(^uintptr(0), ^uintptr(0), 0, curgp)
	}

	// 我们不能在这里称为锁定forEachG，因为这可能是在致命的
	// 抛出/恐慌期间，锁定可能出现故障或直接的
	// 死锁。
	// 
	// 相反，请使用forEachGRace，它不需要锁定。我们不锁定
	// 以防止并发创建新的Gs，但即使使用allglock，我们也可能会错过在这个循环之后创建的Gs。
	forEachGRace(func(gp *g) {
		if gp == me || gp == curgp || readgstatus(gp) == _Gdead || isSystemGoroutine(gp, false) && level < 2 {
			return
		}
		print("\n")
		goroutineheader(gp)
		// 注：gp。m==g.m发生在tracebackothers被
		// 从
		// 系统堆栈调用期间启动的信号处理程序调用时。原始G仍处于
		// 运行状态，我们想打印它的堆栈。
		if gp.m != getg().m && readgstatus(gp)&^_Gscan == _Grunning {
			print("\tgoroutine running on other thread; stack unavailable\n")
			printcreatedby(gp)
		} else {
			traceback(^uintptr(0), ^uintptr(0), 0, gp)
		}
	})
}

// tracebackhextump hextump将部分stk转储到帧周围。sp和框架。fp 
// 用于调试目的。如果地址bad包含在
// 范围内，它也会标记它。
func tracebackHexdump(stk stack, frame *stkframe, bad uintptr) {
	const expand = 32 * goarch.PtrSize
	const maxExpand = 256 * goarch.PtrSize
	// 从帧开始。sp.
	lo, hi := frame.sp, frame.sp
	// 展开以包含框架。fp。
	if frame.fp != 0 && frame.fp < lo {
		lo = frame.fp
	}
	if frame.fp != 0 && frame.fp > hi {
		hi = frame.fp
	}
	// 再扩展一点。
	lo, hi = lo-expand, hi+expand
	// 但不要离画面太远。sp.
	if lo < frame.sp-maxExpand {
		lo = frame.sp - maxExpand
	}
	if hi > frame.sp+maxExpand {
		hi = frame.sp + maxExpand
	}
	// 不要超出堆栈边界。
	if lo < stk.lo {
		lo = stk.lo
	}
	if hi > stk.hi {
		hi = stk.hi
	}

	// 打印十六进制转储。
	print("stack: frame={sp:", hex(frame.sp), ", fp:", hex(frame.fp), "} stack=[", hex(stk.lo), ",", hex(stk.hi), ")\n")
	hexdumpWords(lo, hi, func(p uintptr) byte {
		switch p {
		case frame.fp:
			return '>'
		case frame.sp:
			return '<'
		case bad:
			return '!'
		}
		return 0
	})
}

// isSystemGoroutine报告在堆栈转储和死锁检测器中是否必须忽略goroutine g。这是
// 在运行时启动的任何goroutine。*入口点，运行时除外。main，
// 运行时。handleAsyncEvent（仅限wasm）有时还包括运行时。伦芬克。
// 
// 如果fixed为true，则用户和
// 系统（即终结器goroutine）之间可能存在差异的任何goroutine都被视为用户
// goroutine。
func isSystemGoroutine(gp *g, fixed bool) bool {
	// 将此文件与cmd/trace/trace保持同步。开始：isSystemGoroutine。
	f := findfunc(gp.startpc)
	if !f.valid() {
		return false
	}
	if f.funcID == funcID_runtime_main || f.funcID == funcID_handleAsyncEvent {
		return false
	}
	if f.funcID == funcID_runfinq {
		// 如果它调用
		// 返回用户代码。
		if fixed {
			// 此goroutine可能会有所不同。在固定模式下，ABCFDG 
			return false
		}
		return !fingRunning
	}
	return hasPrefix(funcname(f), "runtime.")
}

// SetCgoTraceback记录三个C函数，用于从C代码中收集
// 回溯信息，并将回溯
// 信息转换为符号信息。这些用于打印
// 使用cgo的程序的堆栈跟踪。
// 
// 可以从信号
// 处理程序调用回溯和上下文函数，因此必须仅使用异步信号安全函数。
// 当程序崩溃时，可能会调用symboler函数，因此必须小心使用内存。
// 函数都不能回调到Go。
// 
// 将使用单个参数调用上下文函数，即
// 指向结构的指针：
// 
// struct{
// context uintptpr 
// /}
// 
// ，这个结构将是
// 
// struct{
// uintptpr\t Context；
// ；
// 
// 如果上下文字段为0，则调用上下文函数来记录当前的回溯上下文。它应该在
// 上下文字段中记录有关当前
// 执行点的任何信息，以便以后生成堆栈跟踪，可能是
// 堆栈指针和PC。在这种情况下，上下文函数将从C代码调用
// 。
// 
// 如果上下文字段不是0，则它是
// 上一次调用上下文函数返回的值。当
// 上下文不再需要时，调用此案例；也就是说，当Go代码将
// 返回给其C代码调用方时。这允许上下文函数释放
// 任何相关资源。
// 
// 虽然上下文函数记录
// 在调用时完成堆栈跟踪是正确的，只需在回溯函数中复制该
// 即可，在一个典型的程序中，会多次调用上下文
// 函数，而不会记录该上下文的
// 回溯。在
// 上下文函数调用中记录完整的堆栈跟踪可能效率低下。
// 
// 将使用单个参数调用回溯函数，
// 指向结构的指针：
// 
// 结构{
// 上下文uintptr 
// SigContext uintptr 
// Buf*uintptr 
// Max uintptr 
// }
// 
// 在C语法中，这个结构将是
// 
// struct{
// uintptr______________________________________________________________；
// 
// 上下文字段将为零，以从
// 当前程序执行点收集回溯。在这种情况下，将从C代码中调用回溯
// 函数。
// 
// 否则，上下文将是调用
// 上下文函数之前返回的值。回溯函数应该从程序执行中保存的点收集堆栈
// 跟踪。回溯
// 函数可以从记录上下文的
// 线程以外的执行线程调用，但仅当已知上下文为
// 有效且不变时。回溯函数也可以在记录
// 。使用
// 上下文的同一线程上的调用堆栈中更深层调用
// 相同的上下文值，可以多次调用回溯函数；通常应该缓存
// 结果，如果可能的话，第一次为特定的
// 上下文值调用该结果时。
// 
// 如果从Unix 
// 系统上的信号处理程序调用回溯函数，SigContext将是传递给信号处理程序的信号上下文参数（C ucontext_t*转换为uintptr__t）。这可能是
// 用于在信号发生的点开始跟踪。如果
// 没有从信号处理程序调用回溯函数，
// SigContext将为零。
// 
// Buf是应存储回溯信息的位置。它应该是PC值，这样Buf[0]就是调用方的PC，Buf[1]就是该函数调用方的PC，依此类推。Max是要存储的最大
// 条目数。函数应该存储一个0到
// 表示堆栈的顶部，或者调用者位于不同的
// 堆栈上，可能是一个Go堆栈。
// 
// 与运行时不同。调用方，返回的PC值在传递给Symboler函数时，应返回调用
// 指令的文件/行。不需要额外的减法，也不合适。
// 
// 在所有平台上，当来自
// Go to C to Go的调用请求堆栈跟踪时，将调用traceback函数。在linux/amd64、linux/ppc64le、
// 和freebsd/amd64上，当执行cgo调用的线程接收到
// 信号时，也会调用回溯函数。
// 回溯函数不应假设何时调用
// 因为Go的未来版本可能会进行额外调用。
// 
// 符号器函数将使用单个参数调用，
// 指向结构的指针：
// 
// 结构{
// PC uintptr
// 文件*字节
// 行号uintptr
// 函数*字节
// Entry uintptpr
// More uintpr
// Data uintpr
// 
// 
// C语法，此结构将是
// 
// struct{
// UINTPRU t PC；
// char*文件；
// UINTPRU t行号；
// char*函数；
// UINTPRU t条目；
// UINTPRU t更多；
// ；
// 
// PC字段将是通过调用回溯
// 函数返回的值。
// 
// 第一次为特定回溯调用函数时，
// 除PC外的所有字段都将为0。如果可能，函数应填写
// 其他字段，如果
// 信息不可用，则将其设置为0/nil。数据字段可用于存储通话中任何有用的
// 信息。“更多”字段应设置为非零
// 如果这台电脑有更多信息，否则为零。如果将更多
// 设置为非零，则将使用相同的
// PC再次调用该函数，并可能返回不同的信息（这是为了将
// 与内联函数一起使用）。如果“更多”为零，则该函数将被
// 调用，并使用回溯中的下一个PC值。当回溯
// 完成时，该函数将在PC设置为
// 零的情况下再次调用；这可以用来释放任何信息。每次调用都将
// 将结构的字段设置为返回时的相同值，但当“更多”字段为零时，PC字段除外。
// 函数不能在调用之间保留结构指针的副本。
// 
// 调用SetCgoTraceback时，version参数是函数预期接收的结构的版本号。
// 当前该值必须为零。
// 
// 符号器函数可能为零，在这种情况下，
// 回溯函数的结果将显示为数字。如果
// 回溯函数为nil，则永远不会调用符号器函数
// 。上下文函数可以是nil，在这种情况下，只能在上下文字段
// 回溯函数。如果上下文函数为nil，那么从Go到C 
// 设置为零的情况下调用
// to Go的调用将不会显示对调用堆栈的C部分的回溯。
// 
// SetCgoTraceback应该只调用一次，最好是从init函数调用。
func SetCgoTraceback(version int, traceback, context, symbolizer unsafe.Pointer) {
	if version != 0 {
		panic("unsupported version")
	}

	if cgoTraceback != nil && cgoTraceback != traceback ||
		cgoContext != nil && cgoContext != context ||
		cgoSymbolizer != nil && cgoSymbolizer != symbolizer {
		panic("call SetCgoTraceback only once")
	}

	cgoTraceback = traceback
	cgoContext = context
	cgoSymbolizer = symbolizer

	// 当C函数调用Go 
	// 函数时，会调用上下文函数。因此，它仅在运行时/cgo中由C代码调用。
	if _cgo_set_context_function != nil {
		cgocall(_cgo_set_context_function, context)
	}
}

var cgoTraceback unsafe.Pointer
var cgoContext unsafe.Pointer
var cgoSymbolizer unsafe.Pointer

// cgoTracebackArg是传递给cgoTraceback的类型。
type cgoTracebackArg struct {
	context    uintptr
	sigContext uintptr
	buf        *uintptr
	max        uintptr
}

// cgoContextArg是传递给上下文函数的类型。
type cgoContextArg struct {
	context uintptr
}

// cgoSymbolizerArg是传递给cgoSymbolizer的类型。
type cgoSymbolizerArg struct {
	pc       uintptr
	file     *byte
	lineno   uintptr
	funcName *byte
	entry    uintptr
	more     uintptr
	data     uintptr
}

// cgoTraceback打印呼叫者的回溯。
func printCgoTraceback(callers *cgoCallers) {
	if cgoSymbolizer == nil {
		for _, c := range callers {
			if c == 0 {
				break
			}
			print("non-Go function at pc=", hex(c), "\n")
		}
		return
	}

	var arg cgoSymbolizerArg
	for _, c := range callers {
		if c == 0 {
			break
		}
		printOneCgoTraceback(c, 0x7fffffff, &arg)
	}
	arg.pc = 0
	callCgoSymbolizer(&arg)
}

// printOneCgoTraceback打印单个cgo调用方的回溯。
// 由于内联，这可以打印多行。
// 返回打印的帧数。
func printOneCgoTraceback(pc uintptr, max int, arg *cgoSymbolizerArg) int {
	c := 0
	arg.pc = pc
	for c <= max {
		callCgoSymbolizer(arg)
		if arg.funcName != nil {
			// 注意，这里没有打印任何参数
			// 信息，甚至连括号都没有。
			// 如果合适的话，符号必须加上。
			println(gostringnocopy(arg.funcName))
		} else {
			println("non-Go function")
		}
		print("\t")
		if arg.file != nil {
			print(gostringnocopy(arg.file), ":", arg.lineno, " ")
		}
		print("pc=", hex(pc), "\n")
		c++
		if arg.more == 0 {
			break
		}
	}
	return c
}

// callCgoSymbolizer调用cgoSymbolizer函数。
func callCgoSymbolizer(arg *cgoSymbolizerArg) {
	call := cgocall
	if panicking > 0 || getg().m.curg != getg() {
		// 我们不希望在系统堆栈中出现恐慌时调用调度程序。
		call = asmcgocall
	}
	if msanenabled {
		msanwrite(unsafe.Pointer(arg), unsafe.Sizeof(cgoSymbolizerArg{}))
	}
	if asanenabled {
		asanwrite(unsafe.Pointer(arg), unsafe.Sizeof(cgoSymbolizerArg{}))
	}
	call(cgoSymbolizer, noescape(unsafe.Pointer(arg)))
}

// cgoContextPCs从cgo回溯中获取PC值。
func cgoContextPCs(ctxt uintptr, buf []uintptr) {
	if cgoTraceback == nil {
		return
	}
	call := cgocall
	if panicking > 0 || getg().m.curg != getg() {
		// 我们不希望在惊慌失措或在系统堆栈上时调用调度程序。
		call = asmcgocall
	}
	arg := cgoTracebackArg{
		context: ctxt,
		buf:     (*uintptr)(noescape(unsafe.Pointer(&buf[0]))),
		max:     uintptr(len(buf)),
	}
	if msanenabled {
		msanwrite(unsafe.Pointer(&arg), unsafe.Sizeof(arg))
	}
	if asanenabled {
		asanwrite(unsafe.Pointer(&arg), unsafe.Sizeof(arg))
	}
	call(cgoTraceback, noescape(unsafe.Pointer(&arg)))
}
